/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.replica.replicaisland;

/**
 * Helper class for interpolating velocity over time given a target velocity and
 * acceleration. The current velocity will be accelerated towards the target
 * until the target is reached. Note that acceleration is effectively an
 * absolute value--it always points in the direction of the target velocity.
 */
public class Interpolator extends AllocationGuard {

	private float mCurrent;
	private float mTarget;
	private float mAcceleration;

	public Interpolator() {
		super();
	}

	// Rather than simply interpolating acceleration and velocity for each time
	// step
	// (as in, position += (velocity * time); velocity += (acceleration *
	// time);),
	// we actually perform the work needed to calculate the integral of velocity
	// with respect to
	// time.
	//
	// The integral of velocity is:
	//
	// integral[(v + aT)dT]
	//
	// Simplified to:
	//
	// vT + 1/2 * aT^2
	//
	// Thus:
	// change in position = velocity * time + (0.5 * acceleration * (time^2))
	// change in velocity = acceleration * time

	public void set(float current, float target, float acceleration) {
		mCurrent = current;
		mTarget = target;
		mAcceleration = acceleration;
	}

	// While this function writes directly to velocity, it doesn't affect
	// position. Instead, the position offset is returned so that it can be
	// blended.
	public float interpolate(float secondsDelta) {
		float oldVelocity = mCurrent;

		// point the acceleration at the target, or zero it if we are already
		// there
		float directionalAcceleration = calculateAcceleration(oldVelocity,
				mAcceleration, mTarget);

		// calculate scaled acceleration (0.5 * acceleration * (time^2))
		float scaledAcceleration;
		scaledAcceleration = scaleAcceleration(directionalAcceleration,
				secondsDelta);

		// calculate the change in position
		float positionOffset = (oldVelocity * secondsDelta)
				+ scaledAcceleration;

		// change in velocity = v + aT
		float newVelocity = oldVelocity
				+ (directionalAcceleration * secondsDelta);

		// check to see if we've passed our target velocity since the last time
		// step. If so, clamp to the target
		if (passedTarget(oldVelocity, newVelocity, mTarget)) {
			newVelocity = mTarget;
		}

		mCurrent = newVelocity;

		return positionOffset;
	}

	public float getCurrent() {
		return mCurrent;
	}

	private boolean passedTarget(float oldVelocity, float newVelocity,
			float targetVelocity) {
		boolean result = false;

		if (oldVelocity < targetVelocity && newVelocity > targetVelocity) {
			result = true;
		} else if (oldVelocity > targetVelocity && newVelocity < targetVelocity) {
			result = true;
		}

		return result;
	}

	// find the magnitude and direction of acceleration.
	// in this system, acceleration always points toward target velocity
	private float calculateAcceleration(float velocity, float acceleration,
			float target) {
		if (Math.abs(velocity - target) < 0.0001f) {
			// no accel needed
			acceleration = 0.0f;
		} else if (velocity > target) {
			// accel must be negative
			acceleration *= -1.0f;
		}

		return acceleration;
	}

	// calculates 1/2 aT^2
	private float scaleAcceleration(float acceleration, float secondsDelta) {
		float timeSquared = (secondsDelta * secondsDelta);
		float scaledAccel = acceleration * timeSquared;
		scaledAccel *= 0.5f;

		return scaledAccel;
	}
}
